Using GL_ARB_multitexture and GL_EXT_vertex_array OpenGL extensions.

By rand0m

Note: for understanding this material it is necessary to know foundations of OOP in C++, foundations of OpenGL programming. Also you need to know foundations of 3D graphics and mathematic. Attention!!! In this article I continue using my VXFile class, so you must read at least Part I of my previous article ("Own 3D file format or Exporter plug-in for 3DS Max").

Part I

Intro: What is OpenGL extension

OpenGL is one of the basic and famous graphical APIs developed by SGI. Many famous games and programs use OpenGL for rendering. OpenGL is Open Graphics Library - "a software interface to graphics hardware", as written in the specification. This library is a set of commands which the programmer can use for rendering. I don't want to tell you what OpenGL is, because you can read it yourself in the specification. I want to help you understand what OpenGL extension is and how it works.

So, OpenGL extension is a hardware-supported procedure which you can use from your program. Extensions are used in OpenGL v1.1 and higher. But they can be different for different video-cards. Standard OpenGL API is placed in OpenGL32.dll (in win32 OS). But the interface for using extensions is provided in another DLL. That depends on your video card driver. For my "S3 Pro Savage DDR" video card it is nbicdnt.dll. For other video adapters it can be something else. There is an interface for using OpenGL extensions in it. DirectX (Direct3D) also uses video card extensions, but they are named differently in it and are called "Caps". For example the OpenGL extension for bump-mapping GL_ARB_texture_env_dot3 is called D3DTEXOPCAPS_DOTPRODUCT3 in DirectX. So OpenGL and DirectX use the same hardware procedures, but coding for them is very different.

So, how can I use video card extensions in OpenGL? First of all I must include "glext.h". It has all declarations for using extensions. To know what extensions are supported by your video card you may use the glGetString(GL_EXTENSIONS) function. So let's write a simple function for determining the extensions of your video card.

Listing 1: Extension determiner function (VC++)

#include "glext.h"
bool    HaveExtension ( const char * ext_name )
{
        const char * ext_string = (const char *)glGetString ( GL_EXTENSIONS );
        const char * start      = ext_string;
        const char * ptr;

        while ( ( ptr = strstr ( start, ext_name ) ) != NULL )
        {
        // we've found, ensure name is exactly ext
                const char * end = ptr + strlen ( ext );

                if ( isspace ( *end ) || *end == '\0' )
                        return true;
                start = end;
        }
return false;
}

Using this function you may ensure what extensions are supported. Call it with the name of extension, like bool isMultiTextring = HaveExtension("GL_ARB_multitexture");

But how can I use the extension? It's very simple. After including "glext.h" you may use wglGetProcAddress(LPCSTR your_gl_extension_function_name) from "wingdi.h". All prototypes of extensions functions are described in "glext.h".

For example:

PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;
...
glActiveTextureARB=(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");

Let's examine this code. PFNGLACTIVETEXTUREARBPROC is a prototype of the glActiveTextureARB function. So we declared a variable of the glActiveTextureARB function and then got its address by the wglGetProcAddress function. Well, you must check up if the required extension is supported, else the program will throw an exception like "Unhandled exception bla bla bla ...".

Chapter I: GL_ARB_multitexture

What is the GL_ARB_multitexture extension? GL_ARB_multitexture means that the video adapter supports more than one texture mapping. To know how many textures are supported you may use the function glGetIntegerv.

For example:

int maxTextureUnits;
glGetIntegerv ( GL_MAX_TEXTURE_UNITS_ARB, &maxTextureUnits );

There are different types of textures blending for it. The basic ones are GL_REPLACE, GL_DECAL, GL_MODULATE and GL_BLEND. They don't have any parameters. That is an example of using multitexture extension:

Listing 1: Using multitexture extension

//Here are our function variables
PFNGLACTIVETEXTUREARBPROC               glActiveTextureARB       = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC         glClientActiveTextureARB = NULL;
PFNGLMULTITEXCOORD1FARBPROC     glMultiTexCoord1f        = NULL;
PFNGLMULTITEXCOORD1FVARBPROC    glMultiTexCoord1fv       = NULL;
PFNGLMULTITEXCOORD2FARBPROC     glMultiTexCoord2f        = NULL;
PFNGLMULTITEXCOORD2FVARBPROC    glMultiTexCoord2fv       = NULL;

//procedure for initializing multitexturing
bool    initMultitexture ()
{
    glActiveTextureARB       = (PFNGLACTIVETEXTUREARBPROC)       wglGetProcAddress ( "glActiveTextureARB"       );
    glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress ( "glClientActiveTextureARB" );
    glMultiTexCoord1f        = (PFNGLMULTITEXCOORD1FARBPROC)     wglGetProcAddress ( "glMultiTexCoord1fARB"     );
    glMultiTexCoord1fv       = (PFNGLMULTITEXCOORD1FVARBPROC)    wglGetProcAddress ( "glMultiTexCoord1fvARB"    );
    glMultiTexCoord2f        = (PFNGLMULTITEXCOORD2FARBPROC)     wglGetProcAddress ( "glMultiTexCoord2fARB"     );
    glMultiTexCoord2fv       = (PFNGLMULTITEXCOORD2FVARBPROC)    wglGetProcAddress ( "glMultiTexCoord2fvARB"    );
    return   glActiveTextureARB       != NULL &&
               glClientActiveTextureARB != NULL &&
               glMultiTexCoord1f        != NULL &&
               glMultiTexCoord1fv       != NULL &&
               glMultiTexCoord2f        != NULL &&
               glMultiTexCoord2fv       != NULL;   
}


void Init1st()
{
        //set first texture
        glActiveTextureARB ( GL_TEXTURE0_ARB );
        glEnable           ( GL_TEXTURE_2D );
        glBindTexture      ( GL_TEXTURE_2D, tex_id1 ); //bind texture image
        glTexEnvi          ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );

}

void Init2nd()
{
        //set second texture
        glActiveTextureARB ( GL_TEXTURE1_ARB );
        glEnable           ( GL_TEXTURE_2D );
        glBindTexture      ( GL_TEXTURE_2D, text_id2 ); //bind texture image
        glTexEnvi          ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
        //GL_ADD blending method
}

void RenderObject()
{
...
        Init1st();
Init2nd();

glBegin(GL_TRIANGLES); 
...      
glNormal3f(n.x, n.y, n.z); //normal for this vertex

        glMultiTexCoord2f(GL_TEXTURE0_ARB, t1.u, t1.v );//first texture coordinates
        glMultiTexCoord2f(GL_TEXTURE1_ARB, t2.u, t2.v );//second texture coordinates


        glVertex3f(v.x, v.y, v.z); //vertex
...
glEnd(); 

}

That is all! But this is only a simple texture blending method. For more complex texture blending you need to use the GL_EXT_texture_env_combine extension. It doesn't add any function, but it adds a new state of texture mapping GL_COMBINE_ARB which may be turned on by the glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_ARB) command. But I think texture blending is a very big topic and it is for another article, so let's stop at this.

Chapter II: GL_EXT_vertex_array and GL_EXT_compiled_vertex_array

The GL_EXT_vertex_array extension is required for buffer rendering. This is an extension of standard OpenGL glDrawArrays, glNormalPointer, glVertexPointer, glTexCoordPointer and glEnableClientState buffer working functions. Using the GL_EXT_vertex_array extension the render process to become faster, because it is done by hardware. glEnableClientState is necessary for enabling buffers of vertexes, texture coordinates and normals. glNormalPointer, glVertexPointer, glTexCoordPointer are used for posting normals, vertexes and texture coordinates buffers accordingly. glDrawArrays is for rendering all data from the buffers. GL_EXT_vertex_array gives access to the next functions:

1. glDrawArraysEXT is for rendering all buffers
2. glVertexPointerEXT is for setting pointer to vertex buffer
3. glNormalPointerEXT is for setting pointer to normal buffer
4. glTexCoordPointerEXT is for setting pointer to texcoords buffer

GL_EXT_compiled_vertex_array gives access to the following functions:

1. glLockArraysEXT is for locking buffer for changing it
2. glUnlockArraysEXT is for unlocking buffer

I show you code better trying to explain all of this stuff.

Listing 2: Using GL_EXT_vertex_array and GL_EXT_compiled_vertex_array extension

//Vertex arrays
PFNGLDRAWARRAYSEXTPROC          glDrawArraysEXT         = NULL;
PFNGLVERTEXPOINTEREXTPROC               glVertexPointerEXT              = NULL;
PFNGLNORMALPOINTEREXTPROC               glNormalPointerEXT              = NULL;
PFNGLTEXCOORDPOINTEREXTPROC     glTexCoordPointerEXT    = NULL;
//Compiled arrays
PFNGLLOCKARRAYSEXTPROC          glLockArraysEXT         = NULL;
PFNGLUNLOCKARRAYSEXTPROC                glUnlockArraysEXT               = NULL;

bool initArrays ()
{
                glDrawArraysEXT = (PFNGLDRAWARRAYSEXTPROC)        wglGetProcAddress ( "glDrawArraysEXT" );
                glVertexPointerEXT      = (PFNGLVERTEXPOINTEREXTPROC) wglGetProcAddress ( "glVertexPointerEXT" );
                glNormalPointerEXT      = (PFNGLNORMALPOINTEREXTPROC) wglGetProcAddress ( "glNormalPointerEXT" );
                glTexCoordPointerEXT= (PFNGLTEXCOORDPOINTEREXTPROC) wglGetProcAddress ( "glTexCoordPointerEXT" );       

                glLockArraysEXT = (PFNGLLOCKARRAYSEXTPROC)   wglGetProcAddress ( "glLockArraysEXT" );
                glUnlockArraysEXT       = (PFNGLUNLOCKARRAYSEXTPROC) wglGetProcAddress ( "glUnlockArraysEXT" );

glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

return  glDrawArraysEXT         != NULL 
&&      glVertexPointerEXT  != NULL 
&&      glNormalPointerEXT  != NULL 
&&      glTexCoordPointerEXT!= NULL;
}



...
VXFile scn; //that is class from my previous article, you may use your own structure
...
static VXRecord   * pv; 

void LoadObject()
{
        if (initArrays()) scn.LoadVXFile("object1.vxa");
}

void PrepareObject(int i)
{
   glLockArraysEXT( 0, scn.Head.numRecords ); //Locking our array data for changing
   pv = &scn.Records[scn.Objects[i].Offset]; //getting pointer to vertex data

   //setting normal pointer
   glNormalPointerEXT(GL_FLOAT,sizeof(VXRecord),scn.Objects[i].numRecords,pv[0].n);
   //setting vertex pointer
   glVertexPointerEXT(3,GL_FLOAT,sizeof(VXRecord),scn.Objects[i].numRecords,pv[0].v); 

   //setting texture coordinates pointer
   glTexCoordPointerEXT(2,GL_FLOAT,sizeof(VXTexCoord),scn.Objects[i].numRecords, scn.Objects[i].TexCoords[0].uv);

   //unlocking our buffer
   glUnlockArraysEXT();
   //draw our buffers
   glDrawArraysEXT (GL_TRIANGLES, 0, scn.Objects[i].numRecords);        
}

void RenderScene()
{
...
for (i=0; i<scn.Head.numObjects; i++)
{
        PrepareObject(i);
}
...
}

I think there isn't any complex code. Note that I used the VXFile class from my previous article, so I didn't describe it here.

Chapter III: Using multitexturing and vertex arrays together

When I wanted to use multitexturing and vertex arrays together I didn't know how to do it, because GL_ARB_multitexture doesn't give a procedure for setting texture coordinates array. It gives only glMultiTexCoord2f. So I decided to explore this problem by myself. I found that I can use same glTexCoordPointer or glTexCoordPointerEXT, and for switching textures I must do with the same glActiveTextureARB. Look at the code below:

Listing 3: Using multitexturing and vertex arrays together

...
    glClientActiveTextureARB(GL_TEXTURE0_ARB);

    glTexCoordPointerEXT(2,GL_FLOAT,sizeof(VXTexCoord),scn.Objects[i].numRecords, scn.Objects[i].TexCoords[0].uv);

    glClientActiveTextureARB(GL_TEXTURE1_ARB);
    glTexCoordPointerEXT(2,GL_FLOAT,sizeof(VXTexCoord),scn.Objects[i].numRecords, scn.Objects[i].TexCoords[0].uv2);

    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glClientActiveTextureARB(GL_TEXTURE0_ARB);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glClientActiveTextureARB(GL_TEXTURE1_ARB);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);


    glActiveTextureARB(GL_TEXTURE0_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, tex_id1 );
 
    glActiveTextureARB(GL_TEXTURE1_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, tex_id2 );

...
        glDrawArraysEXT (GL_TRIANGLES, 0, scn.Objects[i].numRecords);

That is all for now. I tried to explain all for newbies, but I think it may be useful for an experts. For any questions/comments/bug reports/opinions contact me.

rand0m